#include "umath/umath.h"

namespace UCore
{

    const float32 Pi()
    {
        return glm::pi<float32>();
    }
    const float32 HalfPi()
    {
        return glm::half_pi<float32>();
    }
    const float32 TwoPi()
    {
        return glm::two_pi<float32>();
    }
    const float32 Epsilon()
    {
        return glm::epsilon<float32>();
    }

    uBoolean NearBy(const float32 a, const float32 b, float32 deviation)
    {
        return a + deviation == b || a - deviation == b;
    }
    uBoolean NearBy(const UVector2 a, const UVector2 b, float32 deviation)
    {
        UVector2 ret = glm::equal(a, b, deviation);
        return ret.x && ret.y;
    }
    uBoolean NearBy(const UVector3 a, const UVector3 b, float32 deviation)
    {
        UVector3 ret = glm::equal(a, b, deviation);
        return ret.x && ret.y && ret.z;
    }
    uBoolean NearBy(const UVector4 a, const UVector4 b, float32 deviation)
    {
        UVector4 ret = glm::equal(a, b, deviation);
        return ret.x && ret.y && ret.z && ret.w;
    }

    uBoolean Equal(const float32 a, float32 b)
    {
        return NearBy(a, b, Epsilon());
    }
    uBoolean Equal(const UVector2 a, const UVector2 b)
    {
        return a == b;
    }
    uBoolean Equal(const UVector3 a, const UVector3 b)
    {
        return a == b;
    }
    uBoolean Equal(const UVector4 a, const UVector4 b)
    {
        return a == b;
    }

    float32 Sin(float32 x)
    {
        return glm::sin(x);
    }
    float32 Cos(float32 x)
    {
        return glm::cos(x);
    }
    float32 Tan(float32 x)
    {
        return glm::tan(x);
    }
    float32 Arcsin(float32 x)
    {
        return glm::asin(x);
    }
    float32 Arccos(float32 x)
    {
        return glm::acos(x);
    }
    float32 Atctan(float32 x)
    {
        return glm::atan(x);
    }
    float32 Exp(float32 x)
    {
        return glm::exp(x);
    }
    float32 Power(float32 x, float32 r)
    {
        return glm::pow(x, r);
    }
    int32 Sqrt(int32 x)
    {
        return glm::sqrt(x);
    }
    float32 Sqrt(float32 x)
    {
        return glm::sqrt(x);
    }
    float32 Clamp(float32 x, float32 min, float32 max)
    {
        return glm::clamp(x, min, max);
    }
    int32 Ceil(float32 x)
    {
        return glm::ceil(x);
    }
    int32 Floor(float32 x)
    {
        return glm::floor(x);
    }

    float32 Min(float32 a, float32 b)
    {
        return a < b ? a : b;
    }
    float32 Max(float32 a, float32 b)
    {
        return a > b ? a : b;
    }
    int32 Range(int32 min, int32 max)
    {
        return glm::linearRand<int32>(min, max);
    }
    float32 Range(float32 min, float32 max)
    {
        return glm::linearRand<float32>(min, max);
    }
    UVector2 Range(UVector2 min, UVector2 max)
    {
        return glm::linearRand(min, max);
    }
    UVector3 Range(UVector3 min, UVector3 max)
    {
        return glm::linearRand(min, max);
    }
    UVector4 Range(UVector4 min, UVector4 max)
    {
        return glm::linearRand(min, max);
    }

    float32 Dot(const UVector3 &vec1, const UVector3 &vec2)
    {
        return glm::dot(vec1, vec2);
    }
    UVector3 Corss(const UVector3 &vec1, const UVector3 &vec2)
    {
        return glm::cross(vec1, vec2);
    }
    float32 Lerp(const float32 min, const float32 max, const float32 alpha)
    {
        return glm::mix(min, max, alpha);
    }
    UVector2 Lerp(const UVector2 &vec1, const UVector2 &vec2, float32 alpha)
    {
        return glm::mix(vec1, vec2, alpha);
    }
    UVector3 Lerp(const UVector3 &vec1, const UVector3 &vec2, float32 alpha)
    {
        return glm::mix(vec1, vec2, alpha);
    }
    UVector4 Lerp(const UVector4 &vec1, const UVector4 &vec2, float32 alpha)
    {
        return glm::mix(vec1, vec2, alpha);
    }
    UQuaternion Lerp(const UQuaternion &quat1, const UQuaternion &quat2, float32 alpha)
    {
        return glm::lerp(quat1, quat2, alpha);
    }
    UVector2 Normalize(const UVector2 &vec)
    {
        UVector2 ret = glm::normalize(vec);
        return ret;
    }
    UVector3 Normalize(const UVector3 &vec)
    {
        UVector3 ret = glm::normalize(vec);
        return ret;
    }
    UVector4 Normalize(const UVector4 &vec)
    {
        UVector4 ret = glm::normalize(vec);
        return ret;
    }
    const UVector2 &Normalize(UVector2 &vec)
    {
        vec = glm::normalize(vec);
        return vec;
    }
    const UVector3 &Normalize(UVector3 &vec)
    {
        vec = glm::normalize(vec);
        return vec;
    }
    const UVector4 &Normalize(UVector4 &vec)
    {
        vec = glm::normalize(vec);
        return vec;
    }
    UVector2 Translate(const UVector2 &vec, const UVector2 &trans)
    {
        return UVector2(vec.x + trans.x, vec.y + trans.y);
    }
    const UVector2 &Translate(UVector2 &vec, const UVector2 &trans)
    {
        vec.x += trans.x;
        vec.y += trans.y;
        return vec;
    }
    UVector3 Translate(const UVector3 &vec, const UVector3 &trans)
    {
        return UVector3(vec.x + trans.x, vec.y + trans.y, vec.z + trans.z);
    }
    const UVector3 &Translate(UVector3 &vec, const UVector3 &trans)
    {
        vec.x += trans.x;
        vec.y += trans.y;
        vec.z += trans.z;
        return vec;
    }
    UVector4 Translate(const UVector4 &vec, const UVector4 &trans)
    {
        return UVector4(vec.x + trans.x, vec.y + trans.y, vec.z + trans.z, vec.w + trans.w);
    }
    const UVector4 &Translate(UVector4 &vec, const UVector4 &trans)
    {
        vec.x += trans.x;
        vec.y += trans.y;
        vec.z += trans.z;
        vec.w += trans.w;
        return vec;
    }
    UVector2 RotateX(const UVector2 &vec, float32 angle)
    {
        return glm::rotateX(UVector3(vec, 0), angle);
    }
    const UVector2 &RotateX(UVector2 &vec, float32 angle)
    {
        vec = glm::rotateX(UVector3(vec, 0), angle);
        return vec;
    }
    UVector3 RotateX(const UVector3 &vec, float32 angle)
    {
        return glm::rotateX(vec, angle);
    }
    const UVector3 &RotateX(UVector3 &vec, float32 angle)
    {
        vec = glm::rotateX(vec, angle);
        return vec;
    }
    UVector4 RotateX(const UVector4 &vec, float32 angle)
    {
        return glm::rotateX(vec, angle);
    }
    const UVector4 &RotateX(UVector4 &vec, float32 angle)
    {
        vec = glm::rotateX(vec, angle);
        return vec;
    }

    UVector2 RotateY(const UVector2 &vec, float32 angle)
    {
        return glm::rotateY(UVector3(vec, 0), angle);
    }
    const UVector2 &RotateY(UVector2 &vec, float32 angle)
    {
        vec = glm::rotateY(UVector3(vec, 0), angle);
        return vec;
    }
    UVector3 RotateY(const UVector3 &vec, float32 angle)
    {
        return glm::rotateY(vec, angle);
    }
    const UVector3 &RotateY(UVector3 &vec, float32 angle)
    {
        vec = glm::rotateY(vec, angle);
        return vec;
    }
    UVector4 RotateY(const UVector4 &vec, float32 angle)
    {
        return glm::rotateY(vec, angle);
    }
    const UVector4 &RotateY(UVector4 &vec, float32 angle)
    {
        vec = glm::rotateY(vec, angle);
        return vec;
    }

    UVector2 RotateZ(const UVector2 &vec, float32 angle)
    {
        return glm::rotateZ(UVector3(vec, 0), angle);
    }
    const UVector2 &RotateZ(UVector2 &vec, float32 angle)
    {
        vec = glm::rotateZ(UVector3(vec, 0), angle);
        return vec;
    }
    UVector3 RotateZ(const UVector3 &vec, float32 angle)
    {
        return glm::rotateZ(vec, angle);
    }
    const UVector3 &RotateZ(UVector3 &vec, float32 angle)
    {
        vec = glm::rotateZ(vec, angle);
        return vec;
    }
    UVector4 RotateZ(const UVector4 &vec, float32 angle)
    {
        return glm::rotateZ(vec, angle);
    }
    const UVector4 &RotateZ(UVector4 &vec, float32 angle)
    {
        vec = glm::rotateZ(vec, angle);
        return vec;
    }

    UVector2 Scale(const UVector2 &vec, const UVector2 &scale)
    {
        return UVector2(vec.x * scale.x, vec.y * scale.y);
    }
    const UVector2 &Scale(UVector2 &vec, const UVector2 &scale)
    {
        vec.x *= scale.x;
        vec.y *= scale.y;
        return vec;
    }
    UVector3 Scale(const UVector3 &vec, const UVector3 &scale)
    {
        return UVector3(vec.x * scale.x, vec.y * scale.y, vec.z * scale.z);
    }
    const UVector3 &Scale(UVector3 &vec, const UVector3 &scale)
    {
        vec.x *= scale.x;
        vec.y *= scale.y;
        vec.z *= scale.z;
        return vec;
    }
    UVector4 Scale(const UVector4 &vec, const UVector4 &scale)
    {
        return UVector4(vec.x * scale.x, vec.y * scale.y, vec.z * scale.z, vec.w * scale.w);
    }
    const UVector4 &Scale(UVector4 &vec, const UVector4 &scale)
    {
        vec.x *= scale.x;
        vec.y *= scale.y;
        vec.z *= scale.z;
        vec.w *= scale.w;
        return vec;
    }

    UMatrix3 Translate(const UMatrix3 &mat, const UVector3 &trans)
    {
        UMatrix3 ret = glm::translate(UMatrix4(mat), trans);
        return ret;
    }
    const UMatrix3 &Translate(UMatrix3 &mat, const UVector3 &trans)
    {
        mat = glm::translate(UMatrix4(mat), trans);
        return mat;
    }
    UMatrix4 Translate(const UMatrix4 &mat, const UVector3 &trans)
    {
        return glm::translate(mat, trans);
    }
    const UMatrix4 &Translate(UMatrix4 &mat, const UVector3 &trans)
    {
        mat = glm::translate(mat, trans);
        return mat;
    }
    UMatrix4 Rotate(const UMatrix4 &mat, const URotate3 &axis, float32 angle)
    {
        return glm::rotate(mat, angle, axis);
    }
    const UMatrix4 &Rotate(UMatrix4 &mat, const URotate3 &axis, float32 angle)
    {
        mat = glm::rotate(mat, angle, axis);
        return mat;
    }
    UMatrix4 Rotate(const UMatrix4 &mat, const UQuaternion &quat)
    {
        return glm::mat4_cast(quat) * mat;
    }
    const UMatrix4 &Rotate(UMatrix4 &mat, const UQuaternion &quat)
    {
        mat = glm::mat4_cast(quat) * mat;
        return mat;
    }
    UMatrix4 Scale(const UMatrix4 &mat, const UVector3 &scale)
    {
        return glm::scale(mat, scale);
    }
    const UMatrix4 &Scale(UMatrix4 &mat, const UVector3 &scale)
    {
        mat = glm::scale(mat, scale);
        return mat;
    }
    UMatrix3 Transpose(const UMatrix3 &mat)
    {
        return glm::transpose(mat);
    }
    const UMatrix3 &Transpose(UMatrix3 &mat)
    {
        mat = glm::transpose(mat);
        return mat;
    }
    UMatrix4 Transpose(const UMatrix4 &mat)
    {
        return glm::transpose(mat);
    }
    const UMatrix4 &Transpose(UMatrix4 &mat)
    {
        mat = glm::transpose(mat);
        return mat;
    }
}